package process_demo.thread_pool.impl;

/**
 * @author haomin
 * @date 2022/05/05 19:04
 **/
import java.util.concurrent.*;

// 线程池类
public class MyThreadPoolExecutor implements Executor {
    private int currentCoreSize;      // 当前正式员工的数量

    private final int corePoolSize;   // 正式员工的数量上限

    private int currentTemporarySize; // 当前临时员工的数量

    private final int temporaryPoolSize;      // 临时员工的数量上限

    private final ThreadFactory threadFactory;// 创建线程的工厂对象

    // 临时工摸鱼的时间上限
    private final long keepAliveTime;
    private final TimeUnit unit;

    // 传递任务的阻塞队列
    private final BlockingQueue<Runnable> workQueue;

    public MyThreadPoolExecutor(int corePoolSize,
                                int maximumPoolSize,
                                long keepAliveTime,
                                TimeUnit unit,
                                BlockingQueue<Runnable> workQueue,
                                ThreadFactory threadFactory,
                                RejectedExecutionHandler handler) {
        this.corePoolSize = corePoolSize;
        this.temporaryPoolSize = maximumPoolSize - corePoolSize;
        this.workQueue = workQueue;
        this.threadFactory = threadFactory;
        this.keepAliveTime = keepAliveTime;
        this.unit = unit;
    }

    // 向线程池中提交任务
    @Override
    public void execute(Runnable command) {
        // 1. 如果正式员工的数量还低于正式员工的数量上限，则优先创建正式员工处理任务
        // 1.1 需要管理，当前正式员工有多少，正式员工的数量上限有多少？
        if (currentCoreSize < corePoolSize) {
            // 优先创建正式员工进行处理
            // 创建一个线程，这个线程中的任务就是不断地取任务-做任务，但是不需要考虑退出的问题
            CoreJob job = new CoreJob(workQueue, command);
//            Thread thread = new Thread(job);    // 不使用工厂创建的线程
            Thread thread = threadFactory.newThread(job);   // thread 代表的就是正式员工
            String name = String.format("正式员工-%d", currentCoreSize);
            thread.setName(name);

            thread.start();

            // 只是两种不同的策略，没有谁是正确的说法
            // 1. 把 command 放到队列中；command 的执行次序是在队列已有的任务之后
            // 2. 创建正式员工的时候，就把 command 提交给正式员工，让 command 优先执行
            // 我们这里采用第二种方案，主要原因就是 java 官方的就是使用的第二种策略

            currentCoreSize++;
            return;
        }

        // 走到这里，说明正式员工的数量 == 正式员工的上限了
        // 2. 优先把任务放入队列中，如果放入成功，execute 执行结束，否则还需要继续
        // 2.1 需要一个阻塞队列
        // workQueue.put(command); // 带阻塞的放入，是否满足这里的需求？
        // 我们这里希望的是立即得到结果
        boolean success = workQueue.offer(command);
        if (success == true) {
            // 说明放入队列成功
            return;
        }

        // 队列也已经放满了
        // 3. 继续判断，临时工的数量有没有到上限，如果没有到达，创建新的临时工来处理
        if (currentTemporarySize < temporaryPoolSize) {
            // 创建临时工进行处理
            TemporaryJob job = new TemporaryJob(keepAliveTime, unit, workQueue, command);
            //Thread thread = new Thread(job);    // 不使用工厂创建的线程
            Thread thread = threadFactory.newThread(job);   // thread 代表的就是临时员工
            String name = String.format("临时员工-%d", currentTemporarySize);
            thread.setName(name);

            thread.start();

            currentTemporarySize++;
            return;
        }

        // 4. 执行拒绝策略
        // 为了实现方便，暂时不考虑其他策略
        throw new RejectedExecutionException();
    }

    // 一个正式员工线程要完成的工作
    class CoreJob implements Runnable {
        // 需要阻塞队列
        private final BlockingQueue<Runnable> workQueue;
        private Runnable firstCommand;

        CoreJob(BlockingQueue<Runnable> workQueue, Runnable firstCommand) {
            this.workQueue = workQueue;
            this.firstCommand = firstCommand;
        }

        @Override
        public void run() {
            try {
                firstCommand.run();     // 优先先把刚提交的任务先做掉了
                firstCommand = null;    // 这里设置 null 的意思是，不影响 firstCommand 对象被 GC 时的回收

                while (!Thread.interrupted()) {
                    Runnable command = workQueue.take();
                    command.run();
                }
            } catch (InterruptedException ignored) {}
        }
    }

    // 一个临时员工线程要完成的工作
    class TemporaryJob implements Runnable {
        // 需要阻塞队列
        private final BlockingQueue<Runnable> workQueue;
        private final long keepAliveTime;
        private final TimeUnit unit;
        private Runnable firstCommand;

        TemporaryJob(long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, Runnable firstCommand) {
            this.keepAliveTime = keepAliveTime;
            this.unit = unit;
            this.workQueue = workQueue;
            this.firstCommand = firstCommand;
        }

        @Override
        public void run() {
            try {
                firstCommand.run();     // 优先先把刚提交的任务先做掉了
                firstCommand = null;    // 这里设置 null 的意思是，不影响 firstCommand 对象被 GC 时的回收

                // 一旦超过一定时间没有任务，临时工是需要退出的
                // 1. keepAliveTime + unit 记录起来
                // 2. 怎么就知道超过多久没有任务了？如果一定时间内都无法从队列中取出来任务，则认为摸鱼时间够了
                while (!Thread.interrupted()) {
//                Runnable command = workQueue.take();
                    Runnable command = workQueue.poll(keepAliveTime, unit);
                    if (command == null) {
                        // 说明，没有取到任务
                        // 说明超时时间已到
                        // 说明该线程已经 keepAliveTime + unit 时间没有工作了
                        // 所以，可以退出了
                        break;
                    }
                    command.run();
                }
            } catch (InterruptedException ignored) {}
        }
    }
}